package standalone;

import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;


import standalone.springutil.ClassUtils;
import standalone.springutil.HttpHeaders;
import standalone.springutil.HttpRange;
import standalone.springutil.ResourceRegion;


import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.*;

public abstract class AbsResourceHandler {

    private static final boolean contentLengthLongAvailable =
            ClassUtils.hasMethod(ServletResponse.class, "setContentLengthLong", long.class);


    Resource resource ;

    protected abstract MediaType getMediaType(Resource resource);

    public  AbsResourceHandler res(Resource resource){
        this.resource =resource;
        return  this;
    }

    public Resource getResource(){
        return  this.resource;
    }


    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        Resource resource = this.getResource();
        if (resource == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }


        MediaType mediaType = getMediaType( resource);


        // Content phase
        if ("HEAD".equals(request.getMethod())) {
            setHeaders(response, resource, mediaType);
            return;
        }


        if (request.getHeader(HttpHeaders.RANGE) == null) {
            setHeaders(response, resource, mediaType);
            write(resource, mediaType, response);
        }
        else {
            response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");

            try {

                List<HttpRange> httpRanges = getRange(request);
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                if (httpRanges.size() == 1) {
                    ResourceRegion resourceRegion = httpRanges.get(0).toResourceRegion(resource);
                    writeInternal(resourceRegion, mediaType, response,request);
                }
                else {
                    writeInternal(
                            HttpRange.toResourceRegions(httpRanges, resource), mediaType, response,request);
                }
            }
            catch (IllegalArgumentException ex) {
                response.setHeader("Content-Range", "bytes */" + resource.contentLength());
                response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            }
        }
    }

    List<HttpRange>  getRange(HttpServletRequest request){
        HttpHeaders headers = getHeaders(request);
        String value =  headers.getFirst(HttpHeaders.RANGE);
        return HttpRange.parseRanges(value);
    }





    protected void writeInternal(Object object, MediaType mediaType, HttpServletResponse  outputMessage,HttpServletRequest servletRequest)
            throws IOException {

        if (object instanceof ResourceRegion) {
            writeResourceRegion((ResourceRegion) object, outputMessage,servletRequest);
        }
        else {
            Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
            if (regions.size() == 1) {
                writeResourceRegion(regions.iterator().next(), outputMessage,servletRequest);
            }
            else {
                writeResourceRegionCollection((Collection<ResourceRegion>) object, outputMessage,servletRequest);
            }
        }
    }


    protected void writeResourceRegion(ResourceRegion region, HttpServletResponse outputMessage,HttpServletRequest servletRequest) throws IOException {

        HttpHeaders responseHeaders = getHeaders(servletRequest);

        long start = region.getPosition();
        long end = start + region.getCount() - 1;
        Long resourceLength = region.getResource().contentLength();
        end = Math.min(end, resourceLength - 1);
        long rangeLength = end - start + 1;
        responseHeaders.add("Content-Range", "bytes " + start + '-' + end + '/' + resourceLength);
        responseHeaders.setContentLength(rangeLength);


       ;
        for(Map.Entry<String,String> str: responseHeaders.toSingleValueMap().entrySet()){
            outputMessage.addHeader(str.getKey(),str.getValue());
        }


        InputStream in = region.getResource().getInputStream();
        try {
            Utils.copyRange(in, outputMessage.getOutputStream(), start, end);
        }
        finally {
            try {
                in.close();
            }
            catch (IOException ex) {
                // ignore
            }
        }
    }

    private void writeResourceRegionCollection(Collection<ResourceRegion> resourceRegions,
                                               HttpServletResponse outputMessage,HttpServletRequest servletRequest) throws IOException {


        HttpHeaders responseHeaders = getHeaders(servletRequest);

        MediaType contentType = responseHeaders.getContentType();
        String boundaryString = Utils.generateMultipartBoundaryString();
        responseHeaders.set(HttpHeaders.CONTENT_TYPE, "multipart/byteranges; boundary=" + boundaryString);
        OutputStream out = outputMessage.getOutputStream();

        for (ResourceRegion region : resourceRegions) {
            long start = region.getPosition();
            long end = start + region.getCount() - 1;
            InputStream in = region.getResource().getInputStream();
            try {
                // Writing MIME header.
                println(out);
                print(out, "--" + boundaryString);
                println(out);
                if (contentType != null) {
                    print(out, "Content-Type: " + contentType.toString());
                    println(out);
                }
                Long resourceLength = region.getResource().contentLength();
                end = Math.min(end, resourceLength - 1);
                print(out, "Content-Range: bytes " + start + '-' + end + '/' + resourceLength);
                println(out);
                println(out);
                // Printing content
                Utils.copyRange(in, out, start, end);
            }
            finally {
                try {
                    in.close();
                }
                catch (IOException ex) {
                    // ignore
                }
            }
        }

        println(out);
        print(out, "--" + boundaryString + "--");
    }

    private static void println(OutputStream os) throws IOException {
        os.write('\r');
        os.write('\n');
    }

    private static void print(OutputStream os, String buf) throws IOException {
        os.write(buf.getBytes("US-ASCII"));
    }



    private void write(Resource resource, MediaType mediaType, HttpServletResponse response) {
       // addDefaultHeaders(headers, t, contentType);

        try {
            writeInternal(resource, response);
            response.getOutputStream().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



    private void writeInternal(Resource resource, HttpServletResponse response) {
        try {
            InputStream in = resource.getInputStream();
            try {
                Utils.copy(in, response.getOutputStream());
            }
            catch (Exception ex) {
                // ignore, see SPR-13620
            }
            finally {
                try {
                    in.close();
                }
                catch (Throwable ex) {
                    // ignore, see SPR-12999
                }
            }
        }
        catch (Exception ex) {
            // ignore, see SPR-12999
        }
    }


    protected void setHeaders(HttpServletResponse response, Resource resource, MediaType mediaType) throws IOException {
        long length = resource.contentLength();
        if (length > Integer.MAX_VALUE) {
            if (contentLengthLongAvailable) {
                response.setContentLengthLong(length);
            }
            else {
                response.setHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(length));
            }
        }
        else {
            response.setContentLength((int) length);
        }

        if (mediaType != null) {
            response.setContentType(mediaType.toString());
        }

        response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
    }


    public HttpHeaders getHeaders(HttpServletRequest servletRequest) {

        HttpHeaders   headers = new HttpHeaders();

            for (Enumeration<?> headerNames = servletRequest.getHeaderNames(); headerNames.hasMoreElements();) {
                String headerName = (String) headerNames.nextElement();
                for (Enumeration<?> headerValues = servletRequest.getHeaders(headerName);
                     headerValues.hasMoreElements();) {
                    String headerValue = (String) headerValues.nextElement();
                    headers.add(headerName, headerValue);
                }
            }

            // HttpServletRequest exposes some headers as properties: we should include those if not already present
            try {
                MediaType contentType = headers.getContentType();
                if (contentType == null) {
                    String requestContentType = servletRequest.getContentType();
                    if (requestContentType!=null&&requestContentType.length()!=0) {
                        contentType = MediaType.parseMediaType(requestContentType);
                        headers.setContentType(contentType);
                    }
                }

                if (contentType != null &&  contentType.getParameter("charset") == null) {
                    String requestEncoding = servletRequest.getCharacterEncoding();
                    if (requestEncoding!=null&&requestEncoding.length()!=0) {
                        Charset charSet = Charset.forName(requestEncoding);
                        Map<String, String> params = new HashMap<String, String>();
                        params.putAll(contentType.getParameters());
                        params.put("charset", charSet.toString());
                        MediaType newContentType = new MediaType(contentType.getType(), contentType.getSubtype(), params);
                        headers.setContentType(newContentType);
                    }
                }
            }
            catch (Exception ex) {

            }

            if (headers.getContentLength() < 0) {
                int requestContentLength =  servletRequest.getContentLength();
                if (requestContentLength != -1) {
                    headers.setContentLength(requestContentLength);
                }
            }


        return headers;
    }
}
